import wifiManager from "@ohos.wifiManager";
import { TreeSet } from "@kit.ArkTS";
import { expectedHeaderNames, expectedNGConstants, Http2StreamStateIndex,
  kAlpnProtocol,
  kBoundSession,
  kEncrypted,
  kHandle,
  kNoPayloadMethods,
  kSocket,
  kState,
  kType,
  SESSION_FLAGS_READY} from "./constant/Constant";
import { hilog } from "@kit.PerformanceAnalysisKit";
import networkManager from "@ohos.enterprise.networkManager";
import { connection } from "@kit.NetworkKit";
import { EventEmitter } from "./event/EventEmitter";
import { Http2Session } from "./session/Http2Session";
import { LogUtils } from "./LogUtils";

export const IDX_SETTINGS_HEADER_TABLE_SIZE = 0;

export const IDX_SETTINGS_ENABLE_PUSH = 1;

export const IDX_SETTINGS_INITIAL_WINDOW_SIZE = 2;

export const IDX_SETTINGS_MAX_FRAME_SIZE = 3;

export const IDX_SETTINGS_MAX_CONCURRENT_STREAMS = 4;

export const IDX_SETTINGS_MAX_HEADER_LIST_SIZE = 5;

export const IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL = 6;

export const IDX_SETTINGS_FLAGS = 7;

// Maximum number of allowed additional settings
export const MAX_ADDITIONAL_SETTINGS = 10;

export const IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE = 0;

export const IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH = 1;

export const IDX_SESSION_STATE_NEXT_STREAM_ID = 2;

export const IDX_SESSION_STATE_LOCAL_WINDOW_SIZE = 3;

export const IDX_SESSION_STATE_LAST_PROC_STREAM_ID = 4;

export const IDX_SESSION_STATE_REMOTE_WINDOW_SIZE = 5;

export const IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE = 6;

export const IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE = 7;

export const IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE = 8;

export const IDX_STREAM_STATE = 0;

export const IDX_STREAM_STATE_WEIGHT = 1;

export const IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT = 2;

export const IDX_STREAM_STATE_LOCAL_CLOSE = 3;

export const IDX_STREAM_STATE_REMOTE_CLOSE = 4;

export const IDX_STREAM_STATE_LOCAL_WINDOW_SIZE = 5;

export const IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 0;

export const IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS = 1;

export const IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH = 2;

export const IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS = 3;

export const IDX_OPTIONS_PADDING_STRATEGY = 4;

export const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;

export const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;

export const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;

export const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;

export const IDX_OPTIONS_MAX_SETTINGS = 9;

export const IDX_OPTIONS_STREAM_RESET_RATE = 10;

export const IDX_OPTIONS_STREAM_RESET_BURST = 11;

export const IDX_OPTIONS_FLAGS = 12;

function int2ip(n: number): string {
  return `${(n & 0xFF000000) >>> 24}.${(n & 0x00FF0000) >>> 16}.${(n & 0x0000FF00) >>> 8}.${(n & 0xFF)}`
}

export function getLocalIp(): string {
  const ipInfo = wifiManager.getIpInfo()
  console.log('ipInfo : ' + JSON.stringify(ipInfo));
  const localIp = int2ip(ipInfo.ipAddress)
  console.log('localHost : ' + localIp);
  return localIp;
}

export class SafeSet<T> extends TreeSet<T> {
  constructor(...args) {
    super();
    if (args.length > 0) {
      args.forEach((val) => {
        this.add(val);
      });
    }
  }
}


const kValidPseudoHeaders = new SafeSet<string>(
  ':status',
  ':method',
  ':authority',
  ':scheme',
  ':path',
  ':protocol',);


export const assertValidPseudoHeader = function (key) {
  if (!kValidPseudoHeaders.has(key)) {
    throw ("ERR_HTTP2_INVALID_PSEUDOHEADER");
  }
}

const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
export function checkIsHttpToken(val) {
  return tokenRegExp.test(val);
}

export function debugSessionObj(session, message, ...args) {
  debugSession(session[kType], message, ...args);
}
export function debugSession(sessionType, message, ...args) {
  LogUtils.getInstance().info("sessionType: "+sessionType+" message: "+message);
}
export function getAuthority(headers) {
  // For non-CONNECT requests, HTTP/2 allows either :authority
  // or Host to be used equivalently. The first is preferred
  // when making HTTP/2 requests, and the latter is preferred
  // when converting from an HTTP/1 message.
  if (headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY] !== undefined) {
    return headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY];
  }
  if (headers[expectedHeaderNames.HTTP2_HEADER_HOST] !== undefined) {
    return headers[expectedHeaderNames.HTTP2_HEADER_HOST];
  }
}

export const setAndValidatePriorityOptions = ((options) => {
  if (options.weight === undefined) {
    options.weight = expectedNGConstants.NGHTTP2_DEFAULT_WEIGHT;
  } else {
    validateNumber(options.weight, 'options.weight');
  }

  if (options.parent === undefined) {
    options.parent = 0;
  } else {
    validateNumber(options.parent, 'options.parent');
  }

  if (options.exclusive === undefined) {
    options.exclusive = false;
  } else {
    validateBoolean(options.exclusive, 'options.exclusive');
  }

  if (options.silent === undefined) {
    options.silent = false;
  } else {
    validateBoolean(options.silent, 'options.silent');
  }
});
export function isPayloadMeaningless(method) {
  return kNoPayloadMethods.has(method);
}

export function validateString(value: string, label: string) {
  if ((typeof value) == 'string') {
    return true;
  } else {
    console.error(`${label} is not a valid integer.`);
    return false;
  }
}

export function validateNumber(value: number, label: string) {
  if (Number.isInteger(value)) {
    return true;
  } else {
    console.error(`${label} is not a valid integer.`);
    return false;
  }
}

export function validateBoolean(value: boolean, label: string) {
  if (value === true || value === false) {
    return true;
  } else {
    console.error(`${label} is not a valid integer.`);
    return false;
  }
}

export function validateInt32(value: number, label: string, minValue: number) {
  if (Number.isInteger(value) && value >= minValue && value <= 0x7FFFFFFF && value >= -0x80000000) {
    return true;
  } else {
    console.error(`${label} must be an Int32 value between${minValue} and ${0x7FFFFFFF}.`);
    return false;
  }
}

export function validateBuffer(payload, label) {
  if (payload instanceof ArrayBuffer || ArrayBuffer.isView(payload)) {
    return true;
  } else {
    console.error(`${label} must be an ArrayBuffer or a TypedArray.`);
    return false;
  }
}

export function validateFunction(func, functionName) {
  if (typeof func === 'function') {
    return true;
  } else {
    console.error(`${functionName} must be a function.`);
    return false;
  }
}

export function mapToHeaders(mapping) {
  const headers = {};
  for (const key in mapping) {
    if (mapping.hasOwnProperty(key)) {
      // 通常 HTTP 头部字段是大小写不敏感的，但传统上它们的格式是 "camel-Case"。
      // 这里我们将 key 转换为 "camelCase" 格式。
      const headerKey = key.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
      headers[headerKey] = mapping[key];
    }
  }
  return headers;
}
const streamState = new Array(Http2StreamStateIndex.IDX_STREAM_STATE_COUNT);
export function getStreamState(stream) {
  stream.refreshState();
  return {
    state: streamState[Http2StreamStateIndex.IDX_STREAM_STATE],
    weight: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_WEIGHT],
    sumDependencyWeight: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT],
    localClose: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_LOCAL_CLOSE],
    remoteClose: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_REMOTE_CLOSE],
    localWindowSize: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_LOCAL_WINDOW_SIZE],
  };
}

export function setupHandle(session:Http2Session,socket:ESObject, type, options) {
  // If the session has been destroyed, go ahead and emit 'connect',
  // but do nothing else. The various on('connect') handlers set by
  // core will check for session.destroyed before progressing, this
  // ensures that those at least get cleared out.
  if (session.destroyed()) {
    session.nextTickEmit('connect',{data:[{"socket":socket}]})
    return;
  }

  debugSession(type, 'setting up session handle');
  session[kState].flags |= SESSION_FLAGS_READY;



  if (socket.encrypted) {
    session[kAlpnProtocol] = socket.alpnProtocol;
    session[kEncrypted] = true;
  } else {
    // 'h2c' is the protocol identifier for HTTP/2 over plain-text. We use
    // it here to identify any session that is not explicitly using an
    // encrypted socket.
    session[kAlpnProtocol] = 'h2c';
    session[kEncrypted] = false;
  }


  const settings = typeof options.settings === 'object' ?
  options.settings : {};

  session.settings(settings);

  session.nextTickEmit('connect',{data:[{"socket":socket}]})
}

export function initOriginSet(session) {
  let originSet = session[kState].originSet;
  if (originSet === undefined) {
    const socket = session[kSocket];
    session[kState].originSet = originSet = new Set();
    if (socket.servername != null) {
      let originString = `https://${socket.servername}`;
      if (socket.remotePort != null) {
        originString += `:${socket.remotePort}`;
      }
      // We have to ensure that it is a properly serialized
      // ASCII origin string. The socket.servername might not
      // be properly ASCII encoded.
      originSet.add(getURLOrigin(originString));
    }
  }
  return originSet;
}

export function getURLOrigin(url) {
  LogUtils.getInstance().info('getURLOrigin');
  return url;
  // return bindingUrl.getOrigin(url);
}
export function isUint32(val) {
  return val >>> 0 === val;
}
export function socketOnError(session,error) {
  if (session !== undefined) {
    // We can ignore ECONNRESET after GOAWAY was received as there's nothing
    // we can do and the other side is fully within its rights to do so.
    if (error.code === 'ECONNRESET' && session[kState].goawayCode !== null) {
      return session.destroy();
    }
    debugSessionObj(this, 'socket error [%s]', error.message);
    session.destroy(error);
  }
}
export function assertIsObject(value, name) {
  if (value !== undefined &&
    (value === null ||
      typeof value !== 'object' ||
    Array.isArray(value))) {
    throw new Error("ERR_INVALID_ARG_TYPE:" + name + " types || 'Object'")
  }
}


export function isNetWorkAvailable():boolean{
  const isHasDefaultNet = connection.hasDefaultNetSync()
  if (isHasDefaultNet) {
    return true;
  } else {
    return false;
  }
}
